home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
SGI Developer Toolbox 6.1
/
SGI Developer Toolbox 6.1 - Disc 1.iso
/
toolbox
/
src
/
exampleCode
/
opengl
/
xlib
/
pup.c
< prev
next >
Wrap
C/C++ Source or Header
|
1996-11-11
|
43KB
|
1,585 lines
/*
* (c) Copyright 1994, Silicon Graphics, Inc.
* ALL RIGHTS RESERVED
*
* Permission to use, copy, modify, and distribute this software for
* any purpose and without fee is hereby granted, provided that the above
* copyright notice appear in all copies and that both the copyright notice
* and this permission notice appear in supporting documentation, and that
* the name of Silicon Graphics, Inc. not be used in advertising
* or publicity pertaining to distribution of the software without specific,
* written prior permission.
*
* THE MATERIAL EMBODIED ON THIS SOFTWARE IS PROVIDED TO YOU "AS-IS"
* AND WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR OTHERWISE,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY OR
* FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL SILICON
* GRAPHICS, INC. BE LIABLE TO YOU OR ANYONE ELSE FOR ANY DIRECT,
* SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY
* KIND, OR ANY DAMAGES WHATSOEVER, INCLUDING WITHOUT LIMITATION,
* LOSS OF PROFIT, LOSS OF USE, SAVINGS OR REVENUE, OR THE CLAIMS OF
* THIRD PARTIES, WHETHER OR NOT SILICON GRAPHICS, INC. HAS BEEN
* ADVISED OF THE POSSIBILITY OF SUCH LOSS, HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE
* POSSESSION, USE OR PERFORMANCE OF THIS SOFTWARE.
*
* U.S. GOVERNMENT RESTRICTED RIGHTS LEGEND
* Use, duplication, or disclosure by the Government is subject to
* restrictions set forth in FAR 52.227.19(c)(2) or subparagraph
* (c)(1)(ii) of the Rights in Technical Data and Computer Software
* clause at DFARS 252.227-7013 and/or in similar or successor
* clauses in the FAR or the DOD or NASA FAR Supplement.
* Unpublished-- rights reserved under the copyright laws of the
* United States. Contractor/manufacturer is Silicon Graphics,
* Inc., 2011 N. Shoreline Blvd., Mountain View, CA 94039-7311.
*
* OpenGL(TM) is a trademark of Silicon Graphics, Inc.
*/
/*
* menu code with a sordid history.
*/
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <ctype.h>
#include <assert.h>
#include <unistd.h>
#include <bstring.h>
#include <sys/time.h>
#include <sys/times.h>
#include <sys/param.h>
#include <sys/types.h>
#include <stdio.h>
#include <X11/Xlib.h> /* XXX do we need all this shit */
#include <X11/Xutil.h>
#include <X11/Xmd.h>
#include <X11/cursorfont.h>
#include "pup.h"
#define POST_TIMEOUT 160
#define HOG_TIMEOUT 1000
#define MOTION_HISTORY 4
static struct hbuf { int x, y; } history[MOTION_HISTORY];
static int mhead;
static int PostTimeout;
static int HogTimeout;
static int MenuState;
static unsigned int StateTimeout;
static void set_state(int);
static void post_submenu(void);
static void next_event(Display*,XEvent*);
static void timeout_happened(void);
static int history_allows(void);
#define MS_NORMALSTATE 0
#define MS_PULLRIGHTPENDING 1
#define MS_PULLRIGHTACTIVE 2
#define MS_HOGSTATE 3
static char* defaultMenuFont = "-*-helvetica-bold-o-normal--14-100-*";
#define ALLOC(x) malloc(x)
#define FREE(x) free(x)
#define STRDUP(x) strdup(x)
static char m_class[] = "Menu";
static char m_inst[] = "menu";
static void render_outline (Display* , Window , GC *, int , int , int , int );
static void render_fill (Display* , Window , GC *, GC, int , int , int , int );
typedef struct mFont {
XFontStruct* finfo;
char* fontName;
Display *dpy;
struct mFont* next;
} mFont;
#define Menu_itemTopInset 2
#define Menu_itemBottomInset 2
#define Menu_itemLeftInset 4
#define Menu_itemRightInset 4
#define Menu_itemOffset 2
#define Menu_topInset 4
#define Menu_bottomInset 4
#define Menu_leftInset 4
#define Menu_rightInset 4
#define Menu_arrowLeftInset 7
#define Menu_arrowWidth 8
#define Menu_arrowHeight 8
#define Menu_arrowRightInset 0
#define Menu_checkLeftInset 4
#define Menu_checkWidth 11
#define Menu_checkHeight 11
#define Menu_checkRightInset 3
#define Menu_subMenuXOffset -2
#define Menu_subMenuYOffset -3
#define Menu_shadowXOffset 5 + 2 /* +2 for border width */
#define Menu_shadowYOffset 5 + 2
#define Menu_titleYGap 6
#define Menu_arrowAdjust \
(Menu_arrowLeftInset+Menu_arrowWidth+Menu_arrowRightInset)
#define Menu_checkAdjust \
(Menu_checkLeftInset + Menu_checkWidth + Menu_checkRightInset)
#define Menu_widthAdjust (Menu_leftInset+Menu_rightInset)
#define Menu_heightAdjust (Menu_topInset+Menu_bottomInset)
/*---------------------------------------------------------------------- */
typedef struct struct_PopupInfo {
struct struct_PopupInfo *next;
Display* dpy;
int screen;
int overFirst;
GC black,
darkGrey,
grey,
lightGrey,
lightWhiteGrey,
white;
int whitePixel,
blackPixel,
lightGreyPixel;
Colormap colormap;
Visual* visual;
int depth;
Pixmap shadowPixmap;
Pixmap lightWhiteGreyPixmap;
GC titleColors[4];
GC regularColors[4];
GC selectedColors[4];
GC checkBoxOnColors[4];
GC checkBoxOffColors[4];
Cursor arrowCursor;
} PopupInfo;
typedef struct Menu {
PopupInfo* popupInfo;
struct MenuItem* head;
struct MenuItem* tail;
unsigned char popping;
unsigned char isTitle;
unsigned char mapped;
unsigned char button;
unsigned char layoutDone;
unsigned char overFirst;
short width, height;
Window contentWindow;
Window shadowWindow;
int initialX;
struct Menu* menuTitle;
struct MenuItem* currentItem; /* current selected item in this menu */
struct Menu* currentSubMenu; /* current active sub menu (if any) */
struct Menu* stackPrev; /* previous menu in stack from this menu */
struct mFont* menuFont;
int (*menuFunc)(int);
} Menu;
static void draw_check(PopupInfo* , Window , int , int , int , int );
static PopupInfo* create_PopupInfo(Display* , int );
static Menu* create_menu(Display* , int , int);
static void set_title_menu(Menu* , char *);
static void destroy_menu(Menu* );
static int popup_menu(Menu* , int, int , int );
static struct MenuItem* find_item(Menu*,int);
#define MenuItem_normal 0
#define MenuItem_hasCheckBox 1
/* MenuItem::visualState */
#define MenuItem_disabled 0
#define MenuItem_quiet 1
#define MenuItem_selected 2
/* Generic menu item type. Subclasses provide a particular thing to */
/* image as the label part of the menu item. */
typedef struct MenuItem {
struct Menu* parent;
struct MenuItem* nextItem;
struct Menu* subMenu;
char type;
char visualState;
int checkState;
int enabled;
short index; /* item # for client */
int pickValue;
int inset;
short x, y, width, height; /* position in parent */
short trueWidth, trueHeight;
char* label; /* If it is a label, these are */
int len; /* used. If label is zero, it */
struct mFont* font; /* is assumed to be a line */
int useMenuFunc;
int (*itemFunc)(int);
} MenuItem;
static MenuItem* create_item(struct Menu* , int , char* );
static void reset_MenuItem(MenuItem*);
static void resize_item(MenuItem*, int, int);
static void move_item(MenuItem*, int, int);
static void destroy_item(MenuItem* );
static void render_background_item(MenuItem* );
static void enter_item(MenuItem *);
static void leave_item(MenuItem *);
static void render_item(MenuItem *);
static void set_enable_item(MenuItem* , int );
static void set_check_item(MenuItem* , int );
/*---------------------------------------------------------------------*/
typedef struct aFuncElement {
int (*func)();
struct aFuncElement *next;
} aFuncElement;
typedef struct {
aFuncElement *head;
aFuncElement *tail;
} aFuncChain;
static aFuncChain *
create_aFuncChain(void) {
aFuncChain *ret;
ret = ALLOC(sizeof *ret);
bzero(ret, sizeof *ret);
return ret;
}
static void
extend_aFuncChain(aFuncChain* this, int (*newfunc)()) {
aFuncElement *newElement;
if (newfunc) {
newElement = ALLOC(sizeof *newElement);
newElement->next = 0;
newElement->func = newfunc;
if (this->head == 0)
this->head = this->tail = newElement;
else {
this->tail->next = newElement;
this->tail = newElement;
}
}
}
static int
call_aFuncChain(aFuncChain* this, int ret) {
aFuncElement *who;
typedef int (*retintargint)(int);
for (who = this->head; who; who = who->next)
/* ret = ((int (*)(int))(who->func))(ret);*/
ret = ((retintargint)(who->func))(ret);
return ret;
}
static void
destroy_aFuncChain(aFuncChain* this) {
aFuncElement *who, *youwho;
for (who = this->head; who; who = youwho) {
youwho = who->next;
FREE(who);
}
FREE(this);
}
/*---------------------------------------------------------------------*/
mFont* allFontInfo;
static mFont*
create_font(Display* dpy, const char* fname) {
mFont* this = allFontInfo;
XFontStruct *font = 0;
for ( ; this ; this = this->next)
if (this->dpy == dpy) {
if (fname == 0 && this->fontName == 0)
return this;
if (fname == 0 || this->fontName == 0)
continue;
if (strcmp(fname, this->fontName) == 0)
return this;
}
if (fname)
font = XLoadQueryFont(dpy, fname);
if (font == 0) {
font = XLoadQueryFont(dpy, defaultMenuFont);
if (font == 0)
font = XLoadQueryFont(dpy, "fixed");
}
this = ALLOC(sizeof *this);
this->dpy = dpy;
this->next = allFontInfo;
allFontInfo = this;
this->finfo = font;
this->fontName = fname ? STRDUP(fname) : 0;
return this;
}
static int
strwidth_font(mFont* this, char* str, int len) {
int direction, ascent, descent;
XCharStruct size;
XTextExtents(this->finfo, str, len, &direction, &ascent, &descent, &size);
return size.lbearing + size.rbearing;
}
typedef int (*PFI)();
static void
do_addpup(Menu* pup,char *str,va_list args) {
MenuItem* it;
int lastItemNo = 0;
char *cp;
for (it = pup->head; it; it = it->nextItem)
if (it->index)
lastItemNo = it->index;
for (cp = str;;cp++) {
/*
** First collect all the characters of the string, setting
** pieces of flag information as we go ...
*/
PFI entryFunc;
int ignoreMenuFunc;
int isTitle;
int hasUnderline;
int returnValue;
Menu* subMenu;
char collectName[512], *dp;
entryFunc = 0;
ignoreMenuFunc = isTitle = hasUnderline = 0;
returnValue = lastItemNo+1;
subMenu = 0;
dp = collectName;
while (*cp && *cp != '|') {
if (*cp != '%')
*dp++ = *cp++;
else {
switch (*++cp) {
case 't': isTitle = True; break;
case 'l': hasUnderline = True; break;
case 'n': ignoreMenuFunc = True; /* Fall through to next case */
case 'f': entryFunc = va_arg(args, PFI); break;
case 'F': pup->menuFunc = va_arg(args, PFI); break;
case 'm': subMenu = va_arg(args, Menu*); break;
case 'x':
returnValue = strtol(++cp, &cp, 10);
cp--;
break;
case '%': *dp++ = '%'; break;
}
cp++;
}
}
if (dp == collectName)
break;
*dp = 0;
/*
** Now we've collected all the info, add the item(s) to the menu
*/
if (isTitle)
set_title_menu((Menu*) pup, collectName);
else {
it = create_item((Menu*) pup, ++lastItemNo, collectName);
it->pickValue = returnValue;
if (subMenu)
it->subMenu = subMenu;
if (ignoreMenuFunc)
it->useMenuFunc = 0;
it->itemFunc = entryFunc;
if (hasUnderline)
create_item((Menu*) pup, 0, 0);
}
if (*cp == 0)
break;
}
}
long
newpup(Display *dpy, int screen) {
return (long) create_menu(dpy, screen, False);
}
void
addtopup(long pup, char *str, ...) {
va_list args;
va_start(args, str);
(void) do_addpup((Menu*) pup, str, args);
va_end(args);
}
long
defpup(Display *dpy, int scrn, char *str, ...) {
va_list args;
long ret = newpup(dpy, scrn);
va_start(args, str);
(void) do_addpup((Menu*) ret, str, args);
va_end(args);
return ret;
}
long
dopup(long menu) {
int ret;
Menu* pup = (Menu*) menu;
Window junkw;
int junki, x, y;
XQueryPointer(pup->popupInfo->dpy,
RootWindow(pup->popupInfo->dpy, pup->popupInfo->screen),
&junkw, &junkw, &x, &y, &junki, &junki, (unsigned *)&junki);
ret = popup_menu((Menu*) menu, 3, x, y);
XFlush(((Menu*)menu)->popupInfo->dpy);
return ret;
}
void
freepup(long menu) {
if (menu)
destroy_menu((Menu*) menu);
}
void
setpup(long menu, long itemNo, unsigned long arg) {
MenuItem* it;
if (it = find_item((Menu*) menu, itemNo)) {
set_enable_item(it, ((arg & PUP_GREY) == 0));
set_check_item(it, (arg & (PUP_CHECK|PUP_BOX)));
}
}
static void realize_menu(Menu* , int , int );
static void map_menu(Menu* );
static void enter_menu(Window , int , int );
static void leave_item_menu(Menu* );
static void move_to(int , int );
static void enter_item_menu(Menu*,MenuItem* );
static void cancel_menu(Menu* );
static void render_menu(Menu* , int , int );
static void append_item_menu(Menu* , MenuItem* );
static void layout_menu(Menu* );
static Menu* find_menu(Menu* , Window );
static void resize_menu(Menu*, int, int);
static Menu* currentMenu;
static char shadowBits[16*2] = {
0xAA, 0xAA, 0x55, 0x55,
0xAA, 0xAA, 0x55, 0x55,
0xAA, 0xAA, 0x55, 0x55,
0xAA, 0xAA, 0x55, 0x55,
0xAA, 0xAA, 0x55, 0x55,
0xAA, 0xAA, 0x55, 0x55,
0xAA, 0xAA, 0x55, 0x55,
0xAA, 0xAA, 0x55, 0x55,
};
#define checkWidth 16
#define checkHeight 8
static char checkBits[] = {
0x00, 0x0f,
0x86, 0x03,
0xcf, 0x01,
0xec, 0x00,
0x78, 0x00,
0x38, 0x00,
0x10, 0x00,
0x10, 0x00,
};
static char const *
getresource(const char* class, const char* instance, ...) {
return 0;
}
static void
grab_everything(XEvent* ev, PopupInfo* pInfo) {
int press, code;
static have_active_grab;
switch (ev->type) {
case KeyPress:
press = 1;
code = ev->xkey.keycode;
break;
case KeyRelease:
press = 0;
code = ev->xkey.keycode;
break;
case ButtonPress:
press = 1;
code = ev->xbutton.button;
break;
case ButtonRelease:
press = 0;
code = ev->xbutton.button;
break;
default:
return;
}
if (press) {
if (!have_active_grab) {
Cursor c = pInfo->arrowCursor;
Display *dpy = pInfo->dpy;
int gp, gk;
Window window = RootWindow(dpy, pInfo->screen);
#define POINTER_GRAB_MASK (PointerMotionMask|ButtonPressMask|ButtonReleaseMask)
gp = XGrabPointer(dpy, window, True, POINTER_GRAB_MASK,
GrabModeAsync, GrabModeAsync,
None, c, CurrentTime);
/*
gk = XGrabKeyboard(dpy, evw->frame, True, GrabModeAsync,
GrabModeAsync, CurrentTime);
*/
if (/*(gk == GrabSuccess) &&*/ (gp == GrabSuccess)) {
/* winner */
have_active_grab = 1;
} else {
/* loser! no grab now, maybe later */
if (gp == GrabSuccess) {
XUngrabPointer(dpy, CurrentTime);
}
/*
if (gk == GrabSuccess) {
XUngrabKeyboard(dpy, CurrentTime);
}
*/
}
}
} else {
Display *dpy = pInfo->dpy;
if (have_active_grab) {
XUngrabPointer(dpy, CurrentTime);
XUngrabKeyboard(dpy, CurrentTime);
have_active_grab = 0;
}
}
}
static Visual *
find_popup(Display *dpy, int screen, int *depth) {
XVisualInfo *vinfo, template;
int nvisuals;
template.screen = screen;
vinfo = XGetVisualInfo(dpy, VisualScreenMask, &template, &nvisuals);
/* SGI convention is that first visual is PUP visual XXX */
*depth = vinfo->depth;
return vinfo->visual;
}
/*---------------------------------------------------------------------- */
/* "allPopupInfo" points to a list of structures, one per screen, which */
/* contains information about how popup menus are rendered on that */
/* screen. In the future, it may be per screen/visual pair, so that */
/* not all popups have to be in the same planes. */
/*---------------------------------------------------------------------- */
static PopupInfo* allPopupInfo;
static PopupInfo*
create_PopupInfo(Display* dpy, int screen) {
PopupInfo* ret = allPopupInfo;
XGCValues gcv;
int isOverlay = 1;
XColor light_def, medium_def, dark_def;
Window root = RootWindow(dpy, screen);
const char* cname;
while (ret) {
if (ret->dpy == dpy && ret->screen == screen)
return ret;
ret = ret->next;
}
ret = ALLOC(sizeof(*ret));
ret->dpy = dpy;
ret->screen = screen;
ret->visual = find_popup(dpy, screen, &ret->depth);
ret->colormap = XCreateColormap(dpy, root, ret->visual, AllocNone);
ret->arrowCursor = XCreateFontCursor(dpy, XC_arrow);
ret->overFirst = False;
if (cname = getresource(m_class, m_inst, "Timeout", "hogTimeout", 0)) {
HogTimeout = atoi(cname);
FREE((char *)cname);
} else
HogTimeout = HOG_TIMEOUT;
if (cname = getresource(m_class, m_inst, "Timeout", "postTimeout", 0)) {
PostTimeout = atoi(cname);
FREE((char *)cname);
} else
PostTimeout = POST_TIMEOUT;
if (cname = getresource(m_class, m_inst, "OverFirst", "overFirst", 0)) {
if (strcasecmp(cname, "true") == 0)
ret->overFirst = True;
FREE((char *)cname);
}
/* First color in the menu colormap is medium (default grey) */
if (cname = getresource(m_class, m_inst, "Background", "mediumColor", 0)) {
XParseColor(dpy, ret->colormap, cname, &medium_def);
FREE((char *)cname);
} else
medium_def.red = medium_def.green = medium_def.blue = 0xaa00;
XAllocColor(dpy, ret->colormap, &medium_def);
/* 2nd color in the menu colormap is dark (default black) */
if (cname = getresource(m_class, m_inst, "Foreground", "darkColor", 0)) {
XParseColor(dpy, ret->colormap, cname, &dark_def);
FREE((char *)cname);
} else
dark_def.red = dark_def.green = dark_def.blue = 0;
XAllocColor(dpy, ret->colormap, &dark_def);
/* 3nd color in the menu colormap is light (default white) */
if (cname = getresource(m_class, m_inst, "Background", "lightColor", 0)) {
XParseColor(dpy, ret->colormap, cname, &light_def);
FREE((char *)cname);
} else
light_def.red = light_def.green = light_def.blue = 0xff00;
XAllocColor(dpy, ret->colormap, &light_def);
ret->shadowPixmap =
XCreatePixmapFromBitmapData(dpy, root, shadowBits, 16, 16,
isOverlay ? 0 : light_def.pixel,
dark_def.pixel,
ret->depth);
gcv.foreground = dark_def.pixel;
gcv.stipple = XCreatePixmapFromBitmapData(dpy, root, checkBits,
checkWidth, checkHeight, 1, 0, 1);
ret->black = XCreateGC(dpy, ret->shadowPixmap,
GCForeground|GCStipple, &gcv);
XFreePixmap(dpy, gcv.stipple);
gcv.fill_style = FillOpaqueStippled;
gcv.stipple = XCreatePixmapFromBitmapData(dpy, root,
shadowBits, 16, 16, 0, 1, 1);
gcv.foreground = medium_def.pixel;
ret->lightGrey = XCreateGC(dpy, ret->shadowPixmap, GCForeground, &gcv);
gcv.background = dark_def.pixel;
ret->darkGrey = XCreateGC(dpy, ret->shadowPixmap,
GCForeground|GCBackground|GCFillStyle|GCStipple, &gcv);
gcv.foreground = light_def.pixel;
ret->grey = XCreateGC(dpy, ret->shadowPixmap,
GCForeground|GCBackground|GCFillStyle|GCStipple, &gcv);
ret->white = XCreateGC(dpy, ret->shadowPixmap, GCForeground, &gcv);
gcv.background = medium_def.pixel;
ret->lightWhiteGrey = XCreateGC(dpy, ret->shadowPixmap,
GCForeground|GCBackground|GCFillStyle|GCStipple, &gcv);
ret->whitePixel = light_def.pixel;
ret->blackPixel = dark_def.pixel;
ret->lightGreyPixel = medium_def.pixel;
XFreePixmap(dpy, gcv.stipple);
#ifdef DEBUG_RENDER
{ /* Pick the four wild colors to see how things get framed! */
int i;
for (i = 0; i < 4; i++) {
gcv.foreground = i+1;
ret->regularColors[i]=
XCreateGC(dpy, ret->shadowPixmap, GCForeground, &gcv);
}
}
#else
ret->regularColors[0] = ret->white;
ret->regularColors[1] = ret->darkGrey;
ret->regularColors[2] = ret->white;
ret->regularColors[3] = ret->grey;
#endif
ret->titleColors[0] = ret->white;
ret->titleColors[1] = ret->darkGrey;
ret->titleColors[2] = ret->lightWhiteGrey;
ret->titleColors[3] = ret->grey;
ret->selectedColors[0] = ret->white;
ret->selectedColors[1] = ret->darkGrey;
ret->selectedColors[2] = ret->white;
ret->selectedColors[3] = ret->white;
ret->checkBoxOnColors[0] = ret->black;
ret->checkBoxOnColors[1] = ret->white;
ret->checkBoxOnColors[2] = ret->lightGrey;
ret->checkBoxOnColors[3] = ret->white;
ret->checkBoxOffColors[0] = ret->darkGrey;
ret->checkBoxOffColors[1] = ret->darkGrey;
ret->checkBoxOffColors[2] = ret->white;
ret->checkBoxOffColors[3] = ret->darkGrey;
ret->lightWhiteGreyPixmap =
XCreatePixmapFromBitmapData(dpy, root, shadowBits, 16, 16,
medium_def.pixel,
light_def.pixel,
ret->depth);
ret->next = allPopupInfo;
allPopupInfo = ret;
return ret;
}
/*---------------------------------------------------------------------- */
static Menu*
create_menu(Display* dpy, int screen, int isTitle) {
Menu* ret;
XSetWindowAttributes init;
long mask;
ret = (Menu*) ALLOC(sizeof *ret);
bzero(ret, sizeof *ret);
ret->popupInfo = create_PopupInfo(dpy, screen);
if (ret->isTitle = isTitle) {
ret->menuFont = create_font(dpy,
getresource("MenuTitle", "menuTitle", "Font", "font", 0));
init.background_pixel = ret->popupInfo->lightGreyPixel;
mask = CWBackPixel;
} else {
ret->menuFont = create_font(dpy,
getresource(m_class, m_inst, "Font", "font", 0));
init.background_pixmap = ret->popupInfo->lightWhiteGreyPixmap;
mask = CWBackPixmap;
}
init.border_pixel = ret->popupInfo->blackPixel;
init.override_redirect = True;
init.cursor = ret->popupInfo->arrowCursor;
init.colormap = ret->popupInfo->colormap;
init.event_mask = EnterWindowMask|LeaveWindowMask|ButtonPressMask
|ButtonReleaseMask|ExposureMask|PointerMotionMask
|ButtonMotionMask;
ret->contentWindow = XCreateWindow(dpy, RootWindow(dpy, screen),
0, 0,
100, 100,
1,
ret->popupInfo->depth,
InputOutput,
ret->popupInfo->visual,
mask|CWColormap|CWEventMask|CWBorderPixel
|CWOverrideRedirect|CWCursor,
&init
);
init.background_pixmap = ret->popupInfo->shadowPixmap;
init.event_mask = ButtonPressMask | ButtonReleaseMask;
ret->shadowWindow = XCreateWindow(dpy, RootWindow(dpy, screen),
0, 0,
100, 100,
0, ret->popupInfo->depth,
InputOutput, ret->popupInfo->visual,
CWEventMask|CWColormap|CWBackPixmap
|CWBorderPixel|CWOverrideRedirect|CWCursor,
&init
);
return ret;
}
static void
set_title_menu(Menu* this, char *title) {
if (this->isTitle)
return;
if (this->menuTitle)
destroy_menu(this->menuTitle);
this->menuTitle = create_menu(this->popupInfo->dpy,
this->popupInfo->screen, True);
create_item(this->menuTitle, 0, title);
this->layoutDone = 0;
}
static void
destroy_menu(Menu* this) {
if (this->head) destroy_item(this->head);
if (this->menuTitle) destroy_menu(this->menuTitle);
bzero(this, sizeof *this);
FREE(this);
}
static void
map_menu(Menu* this) {
Display *dpy = this->popupInfo->dpy;
if (this->popping) {
XRaiseWindow(dpy, this->shadowWindow);
XResizeWindow(dpy, this->shadowWindow, this->width, this->height);
XResizeWindow(dpy, this->contentWindow, this->width, this->height);
XRaiseWindow(dpy, this->contentWindow);
/*
XMapWindow(dpy, this->shadowWindow); XXXbly
*/
XMapWindow(dpy, this->contentWindow);
XMapWindow(dpy, this->shadowWindow);
this->mapped = True;
}
}
/* XXX try to center menu (title?) under cursor */
/* XXX need a resource for auto-select top item */
/* XXX need a resource for auto-select last item (YEAH) */
static void
realize_menu(Menu* this, int rx, int ry) {
Display *dpy;
int screenWidth;
int screenHeight;
int w, h;
int left, top;
dpy = this->popupInfo->dpy;
screenWidth = DisplayWidth(dpy, this->popupInfo->screen);
screenHeight = DisplayHeight(dpy, this->popupInfo->screen);
if (!this->isTitle) {
layout_menu(this); /* Title layout happens from menu layout */
if (this->overFirst && this->head) {
rx -= this->head->width/2;
ry -= this->head->height/2;
}
}
if (rx < 0)
rx = 0;
w = this->width + 2; /* add in border width */
if (rx + this->width > screenWidth) {
rx = screenWidth - w;
}
h = this->height + 2; /* add in border width */
if (this->menuTitle) {
int menuTitleHeight = this->menuTitle->height + Menu_titleYGap;
if (ry < menuTitleHeight) {
ry = menuTitleHeight;
}
} else if (ry < 0)
ry = 0;
if (ry + h > screenHeight) {
ry = screenHeight - h;
}
if (this->menuTitle) {
realize_menu(this->menuTitle, rx, ry - this->menuTitle->height
- Menu_titleYGap);
}
XMoveWindow(dpy, this->contentWindow, rx, ry);
XMoveWindow(dpy, this->shadowWindow, rx + Menu_shadowXOffset,
ry + Menu_shadowYOffset);
this->popping = True;
this->currentItem = 0;
this->currentSubMenu = 0;
this->stackPrev = 0;
map_menu(this);
}
static Menu*
find_menu(Menu* this, Window lookfor) {
MenuItem* it;
if (lookfor == this->contentWindow)
return this;
if (this->menuTitle && this->menuTitle->contentWindow == lookfor)
return this->menuTitle;
for (it = this->head; it; it = it->nextItem) {
Menu* foundBelow;
if (it->subMenu && (foundBelow = find_menu(it->subMenu, lookfor)))
return foundBelow;
}
return False;
}
static int
popup_menu(Menu* this, int b, int rx, int ry) {
static int alreadyUp = 0;
PopupInfo* pInfo = this->popupInfo;
Display *dpy = pInfo->dpy;
this->button = b;
if (alreadyUp) {
fprintf(stderr, "can't call dopup from a menu function\n");
return -1;
}
XInstallColormap(dpy, pInfo->colormap);
/*XFlush(dpy);XXXbly*/
this->overFirst = pInfo->overFirst;
realize_menu(this, rx, ry);
{
/* should check that the button is still down ... */
XEvent ev;
ev.type = ButtonPress;
grab_everything(&ev, pInfo); /* XXX */
}
alreadyUp = 1;
/* Spin, waiting for event that dismisses the menu */
currentMenu = this;
set_state(MS_NORMALSTATE);
for (;;) {
XEvent ev;
next_event(dpy, &ev);
switch (ev.type) {
case KeyPress:
case KeyRelease:
case ButtonPress:
grab_everything(&ev, pInfo);
break;
case ButtonRelease:
grab_everything(&ev, pInfo);
if (ev.xbutton.button == b) {
MenuItem* result = currentMenu->currentItem;
aFuncChain *chain;
int pick = -1;
chain = create_aFuncChain();
if (result) {
extend_aFuncChain(chain, result->itemFunc);
if (result->useMenuFunc) {
Menu* amen;
for (amen = result->parent; amen != NULL; amen = amen->stackPrev)
extend_aFuncChain(chain, amen->menuFunc);
}
}
cancel_menu(this);
if (result && result->enabled && !result->subMenu)
pick = call_aFuncChain(chain, result->pickValue);
alreadyUp = 0;
destroy_aFuncChain(chain);
return pick;
}
break;
case MotionNotify:
{
int haveXY = False;
int lx, ly;
Window ww = currentMenu->contentWindow;
do {
if (ev.xmotion.window == ww) {
history[mhead].x = lx = ev.xmotion.x;
history[mhead].y = ly = ev.xmotion.y;
mhead = (mhead + 1) % MOTION_HISTORY;
haveXY = True;
}
} while (XCheckMaskEvent(dpy, ButtonMotionMask, &ev));
if (haveXY)
move_to(lx, ly);
}
break;
case EnterNotify:
case LeaveNotify:
{
Window window;
int x, y, type;
do {
x = ev.xcrossing.x;
y = ev.xcrossing.y;
type = ev.type;
window = ev.xcrossing.window;
} while (XCheckMaskEvent(dpy,
EnterWindowMask|LeaveWindowMask, &ev));
if (type == EnterNotify)
enter_menu(window, x, y);
else if (MenuState != MS_HOGSTATE)
leave_item_menu(currentMenu);
}
break;
case Expose:
{
Window theWindow = ev.xexpose.window;
Menu* theMenu = find_menu(this, theWindow);
if (theMenu) {
int topY = theMenu->height;
int bottomY = 0;
do {
if (ev.xexpose.y < topY) topY = ev.xexpose.y;
if (ev.xexpose.y+ev.xexpose.height > bottomY)
bottomY = ev.xexpose.y+ev.xexpose.height;
if (ev.xexpose.count == 0)
render_menu(theMenu, topY, bottomY);
} while
(XCheckWindowEvent(dpy, theWindow, ExposureMask, &ev));
}
break;
}
}
}
}
static void
enter_menu(Window evw, int x, int y) {
Menu* m;
if ((m = currentMenu->currentSubMenu) && (evw == m->contentWindow)) {
/* Entered the current menus sub menu. Make the sub menu the new */
/* current menu. */
set_state(MS_NORMALSTATE);
m->stackPrev = currentMenu;
currentMenu = m;
assert(currentMenu->currentItem == 0);
assert(currentMenu->currentSubMenu == 0);
move_to(x, y);
return;
}
if (MenuState == MS_HOGSTATE
&& evw != currentMenu->currentSubMenu->contentWindow)
return;
set_state(MS_NORMALSTATE);
if ((m = currentMenu->stackPrev) && (evw == m->contentWindow)) {
/* Entered the current menus parent. Don't cancel the sub menu */
/* automatically. Instead, just revert the currentMenu. */
leave_item_menu(currentMenu);
currentMenu->stackPrev = 0;
currentMenu = m;
assert(currentMenu->currentItem != 0);
assert(currentMenu->currentSubMenu != 0);
move_to(x, y);
return;
}
m = currentMenu;
while (m) {
if (m->contentWindow == evw) {
while (currentMenu != m) {
leave_item_menu(currentMenu);
currentMenu = currentMenu->stackPrev;
}
move_to(x, y);
return;
}
m = m->stackPrev;
}
leave_item_menu(currentMenu);
}
static void
leave_item_menu(Menu* this) {
set_state(MS_NORMALSTATE);
if (this->currentItem) {
leave_item(this->currentItem);
this->currentItem = 0;
if (this->currentSubMenu) {
cancel_menu(this->currentSubMenu);
this->currentSubMenu = 0;
}
}
}
/*
** Look at the motion history buffer and see if the delta in x is
** greater than or equal to the delta in y
*/
static int
history_allows(void) {
struct hbuf *oldest, *newest;
int dx, dy;
newest = history + (mhead+MOTION_HISTORY-1)%MOTION_HISTORY;
oldest = history + mhead;
dx = newest->x - oldest->x;
dy = newest->y - oldest->y;
return dx > 0 && dy > 0 && dx*2 > dy;
}
static void
move_to(int evx, int evy)
{
MenuItem* it = currentMenu->head;
if (MenuState == MS_HOGSTATE)
return;
while (it) {
/* See if evx & evyy are inside an item */
if ((evx >= it->x) && (evx < it->x + it->width) &&
(evy >= it->y) && (evy < it->y + it->height)) {
if (it != currentMenu->currentItem) {
if (MenuState == MS_PULLRIGHTACTIVE && history_allows()) {
set_state(MS_HOGSTATE);
return;
}
set_state(MS_NORMALSTATE);
leave_item_menu(currentMenu);
enter_item_menu(currentMenu, it);
currentMenu->initialX = evx;
}
return;
}
it = it->nextItem;
}
if (MenuState == MS_PULLRIGHTACTIVE && history_allows())
set_state(MS_HOGSTATE);
if (MenuState != MS_HOGSTATE)
leave_item_menu(currentMenu);
}
static void
enter_item_menu(Menu* this,MenuItem* it) {
Display* dpy = this->popupInfo->dpy;
Menu* sub = it->subMenu;
this->currentItem = it;
enter_item(it);
if (it->enabled && sub) {
this->currentSubMenu = sub;
set_state(MS_PULLRIGHTPENDING);
}
}
static void
post_submenu() {
Menu* this = currentMenu;
MenuItem* it = currentMenu->currentItem;
Display* dpy = currentMenu->popupInfo->dpy;
int rootx, rooty;
Window child;
XTranslateCoordinates(dpy, this->contentWindow,
RootWindow(dpy, this->popupInfo->screen),
it->x + it->width + Menu_subMenuXOffset,
it->y + Menu_subMenuYOffset,
&rootx, &rooty, &child);
this->currentSubMenu->overFirst = False;
realize_menu(this->currentSubMenu, rootx, rooty);
XFlush(dpy);
set_state(MS_PULLRIGHTACTIVE);
}
static void
cancel_menu(Menu* this) {
Display* dpy = this->popupInfo->dpy;
XUnmapWindow(dpy, this->shadowWindow);
XUnmapWindow(dpy, this->contentWindow);
set_state(MS_NORMALSTATE);
this->mapped = False;
if (this->menuTitle)
cancel_menu(this->menuTitle);
this->popping = False;
leave_item_menu(this);
this->stackPrev = 0;
}
static void
render_menu(Menu* this, int startY, int endY) {
PopupInfo* pi = this->popupInfo;
MenuItem* it = this->head;
if (!this->mapped)
return;
render_outline(pi->dpy, this->contentWindow,
this->isTitle ? pi->titleColors : pi->regularColors,
0, 0, this->width, this->height);
while (it) {
if (it->y + it->height >= startY && it->y < endY)
render_item(it);
it = it->nextItem;
}
}
static void
append_item_menu(Menu* this, MenuItem* it) {
if (this->head)
this->tail->nextItem = it;
else
this->head = it;
this->tail = it;
}
static void
layout_menu(Menu* this) {
/* Find maximum width and total height. Position each menu-item at */
/* its final x & y relative to the menu window. */
short maxWidth = 0;
int y = Menu_topInset;
int checkAdjust = 0;
MenuItem* it = this->head;
int arrowAdjust = 0;
if (this->layoutDone)
return;
this->layoutDone = 1;
while (it) {
short iw, ih;
MenuItem* next = it->nextItem;
reset_MenuItem(it);
iw = it->width;
ih = it->height;
if (it->subMenu) {
arrowAdjust = Menu_arrowAdjust;
if (ih < Menu_arrowHeight) {
ih = Menu_arrowHeight;
resize_item(it, iw, ih);
}
}
if (it->type == MenuItem_hasCheckBox)
checkAdjust = Menu_checkAdjust;
if (it->label && ih < Menu_checkHeight) ih = Menu_checkHeight;
if (iw > maxWidth) maxWidth = iw;
move_item(it, Menu_leftInset, y);
y += ih;
if (it->label && next)
y += Menu_itemOffset;
it = next;
}
maxWidth +=
checkAdjust + arrowAdjust + Menu_itemLeftInset + Menu_itemRightInset;
this->width = maxWidth + Menu_widthAdjust;
if (this->menuTitle) {
int tw;
layout_menu(this->menuTitle);
tw = this->menuTitle->width;
if (this->width <= tw) {
int adjust = tw - this->width;
this->width = tw;
maxWidth += adjust;
} else
resize_menu(this->menuTitle,this->width,this->menuTitle->height);
}
this->height = y + Menu_bottomInset;
/* Now fix the widths of the menu items */
for (it = this->head; (it) ; it = it->nextItem) {
it->inset = checkAdjust;
resize_item(it, maxWidth, it->height);
}
}
static void
resize_menu(Menu* this, int w, int h) {
this->width = w;
this->height = h;
}
static MenuItem*
find_item(Menu* this, int itemNo) {
MenuItem* it;
for (it = this->head; it; it = it->nextItem)
if (it->index == itemNo)
return it;
return 0;
}
/*----------------------------------------------------------------------*/
static void
draw_arrow (Display* dpy, Window win, GC in, GC edge, int x, int y, int w, int h) {
XPoint p[5];
p[0].x = p[4].x = x;
p[0].y = p[4].y = y;
p[1].x = x + w - 1;
p[1].y = y + (h / 2) - 1;
p[2].x = p[1].x;
p[2].y = p[1].y + 1;
p[3].x = x;
p[3].y = y + h - 1;
XFillPolygon(dpy, win, in, p, 4, Convex, CoordModeOrigin);
XDrawLines(dpy, win, edge, p, 5, CoordModeOrigin);
}
/*----------------------------------------------------------------------*/
static MenuItem*
create_item(Menu* m, int ix, char* label) {
MenuItem* this = ALLOC(sizeof *this);
this->parent = m;
append_item_menu(m, this);
this->nextItem = 0;
this->type = MenuItem_normal;
this->enabled = True;
this->visualState = MenuItem_quiet;
this->checkState = 0;
this->inset = 0;
this->subMenu = 0;
this->index = this->pickValue = ix;
this->useMenuFunc = True;
if (label) {
this->label = STRDUP(label);
this->len = strlen(label);
this->trueWidth = this->width =
strwidth_font(m->menuFont, label, this->len);
this->trueHeight = this->height = Menu_itemTopInset
+ m->menuFont->finfo->ascent + m->menuFont->finfo->descent
+ Menu_itemBottomInset;
} else {
this->label = 0;
this->trueWidth = this->width = 0;
this->trueHeight = this->height = 3;
this->enabled = False;
}
return this;
}
static void
destroy_item(MenuItem* this) {
if (this->nextItem) destroy_item(this->nextItem);
if (this->label) FREE(this->label);
bzero(this, sizeof *this);
FREE(this);
}
static void
set_enable_item(MenuItem* this, int on) {
this->visualState = on ? MenuItem_quiet : MenuItem_disabled;
this->enabled = on;
}
static void
set_check_item(MenuItem* this, int on) {
this->type = (on ? MenuItem_hasCheckBox : MenuItem_normal);
this->checkState = on;
this->parent->layoutDone = False;
}
static void
render_background_item(MenuItem* this) {
PopupInfo *pi = this->parent->popupInfo;
Display* dpy = pi->dpy;
Window win = this->parent->contentWindow;
if (this->visualState == MenuItem_selected) {
render_fill(dpy, win, pi->selectedColors, pi->white,
this->x, this->y, this->width, this->height);
}
if (this->subMenu) {
int xx = this->x + this->width
-Menu_itemRightInset-Menu_arrowRightInset-Menu_arrowWidth;
int yy = this->y + (this->height - Menu_arrowHeight) / 2;
GC fg, bg;
if (this->visualState == MenuItem_disabled)
fg = pi->lightWhiteGrey, bg = pi->darkGrey;
else
fg = pi->white, bg = pi->black;
draw_arrow(dpy, win, fg, bg, xx, yy,
Menu_arrowWidth, Menu_arrowHeight);
XDrawLine(dpy, win, bg, xx, yy + Menu_arrowHeight,
xx + Menu_arrowWidth - 1, yy + Menu_arrowHeight/2+1);
}
if (this->type == MenuItem_hasCheckBox)
draw_check(pi, win, this->checkState, this->x, this->y, this->height);
}
static void
enter_item(MenuItem *this) {
if (this->enabled) {
this->visualState = MenuItem_selected;
render_item(this);
}
}
static void
leave_item(MenuItem *this) {
if (this->enabled) {
this->visualState = MenuItem_quiet;
XClearArea(this->parent->popupInfo->dpy, this->parent->contentWindow,
this->x, this->y,
this->width, this->height, False);
if (this->parent->mapped)
render_item(this);
}
}
static void
render_item(MenuItem *this) {
PopupInfo *pi = this->parent->popupInfo;
Window w = this->parent->contentWindow;
if (this->label) {
int xx = this->x + Menu_itemLeftInset;
int yy = this->y + Menu_itemTopInset;
GC gc;
render_background_item(this);
if (this->visualState == MenuItem_disabled)
gc = pi->darkGrey;
else
gc = pi->black;
XSetFont(pi->dpy, gc, this->parent->menuFont->finfo->fid);
XDrawString(pi->dpy, w, gc,
xx+this->inset, yy + this->parent->menuFont->finfo->ascent,
this->label, this->len);
} else {
int yy = this->y;
int xr = this->x + this->width;
XDrawLine(pi->dpy, w, pi->darkGrey, this->x, yy, xr, yy);
yy++;
XDrawLine(pi->dpy, w, pi->white, this->x, yy, xr, yy);
}
}
static void
resize_item(MenuItem* this, int w, int h) {
this->width = w;
this->height = h;
}
static void
reset_MenuItem(MenuItem* this) {
this->width = this->trueWidth;
this->height = this->trueHeight;
}
static void
move_item(MenuItem* this, int x, int y) {
this->x = x;
this->y = y;
}
static void
render_edges (Display* dpy, Window win, GC *colors, int x, int y, int w, int h) {
XPoint p[3];
p[0].x = x; /* Start in lower left */
p[0].y = y + h - 1;
p[1].x = x; /* Go to top left */
p[1].y = y;
p[2].x = x + w - 2; /* Finish in upper right */
p[2].y = y;
XDrawLines(dpy, win, colors[0], p, 3, CoordModeOrigin);
p[0].x = x + 1; /* Start in lower left */
p[0].y = y + h - 1;
p[1].x = x + w - 1; /* Go to lower right */
p[1].y = y + h - 1;
p[2].x = x + w - 1; /* Finish in upper right */
p[2].y = y;
XDrawLines(dpy, win, colors[1], p, 3, CoordModeOrigin);
}
static void
render_outline (Display* dpy, Window win, GC *colors, int x, int y, int w, int h) {
render_edges(dpy, win, colors, x, y, w, h);
render_edges(dpy, win, colors+2, x+1, y+1, w-2, h-2);
}
static void
render_fill(Display* dpy, Window win, GC *colors, GC fillColor,
int x, int y, int w, int h) {
render_outline(dpy, win, colors, x, y, w, h);
XFillRectangle(dpy, win, fillColor, x+2, y+2, w-4, h-4);
}
static void
draw_check(PopupInfo* pi, Window w, int on, int x, int y, int ih) {
GC *gcs;
gcs = (on ? pi->checkBoxOnColors : pi->checkBoxOffColors);
x += Menu_checkLeftInset;
y += (ih - Menu_checkHeight) / 2;
if (on)
render_fill(pi->dpy, w, gcs, pi->lightGrey,
x, y, Menu_checkWidth, Menu_checkHeight);
if (on & PUP_CHECK) {
int xo = (x + 2) % checkWidth;
int yo = (y + 2) % checkHeight;
XSetFillStyle(pi->dpy, pi->black, FillStippled);
XSetTSOrigin(pi->dpy, pi->black, xo, yo);
XFillRectangle(pi->dpy, w, pi->black,
x + 2, y + 2, checkWidth, checkHeight);
XSetFillStyle(pi->dpy, pi->black, FillSolid);
}
}
static void
set_state(int newState) {
int delay;
struct tms t;
switch (MenuState = newState) {
case MS_PULLRIGHTACTIVE:
case MS_NORMALSTATE: StateTimeout = 0; return;
case MS_HOGSTATE: delay = HogTimeout; break;
case MS_PULLRIGHTPENDING: delay = PostTimeout; break;
}
delay = (delay * HZ) / 1000;
StateTimeout = times(&t) + delay;
}
static void
timeout_happened(void) {
switch (MenuState) {
case MS_PULLRIGHTPENDING:
post_submenu();
break;
case MS_HOGSTATE: {
struct hbuf *last;
cancel_menu(currentMenu->currentSubMenu);
currentMenu->currentSubMenu = 0;
set_state(MS_NORMALSTATE);
last = history + (mhead+MOTION_HISTORY-1)%MOTION_HISTORY;
move_to(last->x, last->y);
break;
}
default:
set_state(MS_NORMALSTATE);
break;
}
}
static void
next_event(Display* dpy, XEvent* pEv) {
struct tms tms;
if (StateTimeout) {
unsigned int now = times(&tms);
if (now > StateTimeout)
timeout_happened();
if (StateTimeout) {
int fd;
if (XEventsQueued(dpy, QueuedAfterFlush) == 0) {
fd = ConnectionNumber(dpy);
do {
struct timeval t;
fd_set fds;
unsigned int delta;
FD_ZERO(&fds);
FD_SET(fd, &fds);
delta = StateTimeout - now;
t.tv_sec = delta / HZ;
t.tv_usec = (delta % HZ) * 1000000 / HZ;
switch (select(fd+1, &fds, 0, 0, &t)) {
case 0: /* timeout happened */
timeout_happened();
if (StateTimeout == 0) {
fd = -1;
break;
}
/* else do it again, fall through to .. */
case -1: /* Error, should call select again */
now = times(&tms);
break;
default: /* Data is ready */
if (XEventsQueued(dpy, QueuedAfterReading) == 0)
fprintf(stderr, "botch\n"), abort();
fd = -1;
break;
}
} while (fd >= 0);
}
}
}
XNextEvent(dpy, pEv);
}